package com.ds.server;
import java.math.BigDecimal;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import org.lwjgl.util.vector.Vector3f;
import com.ds.support.GameInfo;
import com.ds.support.GameStatus;
import com.ds.server.support.CollisionCtrl;
import com.ds.server.support.ServerCar;

// the class defined the logic in the server side
public class RMIServerImpl extends UnicastRemoteObject implements RMIServerFramwork { 

	private static final long serialVersionUID = -918923487973606075L;
	
	private GameStatus gameStatus; // game status
	private boolean[] availableID; // indicator of which ID is available to use 
	private boolean[] quitedCar;   // indicator of which player is quit from the game
	private ArrayList<ServerCar> carList;
	private final int PLAYER_NUMBER = 2; // the number of players one game should have
	// a list of staring points
	private final static ArrayList<Vector3f> startPos;
	private int winerID = -1; // the ID of the game's winner
	// a list of time stamps for judging whether a client is connected
	private long[] lastConnection;
	// the connection detecting thread
	private NetworkDetectThread detectThread;
	
	static {
		startPos = new ArrayList<Vector3f>();
		startPos.add(new Vector3f(480f, 690f, 1f));
		startPos.add(new Vector3f(570f, 690f, 1f));
		startPos.add(new Vector3f(480f, 600f, 1f));
		startPos.add(new Vector3f(570f, 600f, 1f));
	}

	public RMIServerImpl () throws RemoteException {
		this.gameStatus = GameStatus.READY;
		this.carList = new ArrayList<ServerCar>();
		this.availableID = new boolean[PLAYER_NUMBER];
		this.quitedCar = new boolean[PLAYER_NUMBER];
		this.lastConnection = new long[PLAYER_NUMBER];
		for (int i = 0; i < PLAYER_NUMBER; i++) {
			availableID[i] = true;
			quitedCar[i] = false;
		}
		detectThread = new NetworkDetectThread();
		Thread th = new Thread(this.detectThread);
		th.start();		
	}

	public int register(int carType) {
		if (hasPlace()) {
			System.out.println("carList: "+carList);
			System.out.println("carType: "+carType);
			int carID = getID();
			ServerCar aCar = new ServerCar();
			aCar.updatePos(startPos.get(carID));
			aCar.setCarType(carType);
			if (carList.size() == carID) {
				carList.add(aCar);
			}
			else {
				carList.set(carID, aCar);
			}
			if (!hasPlace()) {
				gameStatus = GameStatus.RUNING;
			}
			lastConnection[carID]=System.nanoTime() / 1000000000;
			return carID;
		}
		else {
			return -1;
		}
	}
		
	public void moveCar(int carID, String pos) {
    	//System.out.println("car "+carID+" move " + pos);
    	ServerCar currentCar = carList.get(carID);
    	currentCar.calPos(pos);
    	currentCar.setCollisionStatus(CollisionCtrl.raceCollison(currentCar));
    	
    	// collision detecting
 		for (int i = 0; i < carList.size(); i++) {
 			if ( i != carID) {
 				if(CollisionCtrl.carCollision(currentCar, carList.get(i))){
 					currentCar.setCollisionStatus(true);
 					carList.get(i).setCollisionStatus(true);
 				}
 			}
 		}    
    	carList.set(carID, currentCar);

    	// the game result judging 
    	judgeGame();
    }

    public GameInfo getGameInfo() {
    	float[] speedList = new float[carList.size()];
    	boolean[] collisionStatus = new boolean[carList.size()]; 
    	ArrayList<Vector3f> carPositions = new ArrayList<Vector3f>();
    	int[] carTypes = new int[carList.size()];
    	int[] loopList = new int[carList.size()];
    	float[] hpList = new float[carList.size()];
    	for (int i = 0; i < carList.size(); i++) {
    		ServerCar currentCar = carList.get(i);
    		float speed = currentCar.getSpeed();
    		BigDecimal b = new BigDecimal(speed); 
    		float speedF1 = b.setScale(1, BigDecimal.ROUND_HALF_UP).floatValue(); 
    		speedList[i] = speedF1;
    		calHp(currentCar, speedF1);
    		hpList[i] = currentCar.currentHp;
    		carPositions.add(currentCar.getPos());
    		carTypes[i] = currentCar.getCarType();
    		loopList[i] = currentCar.getLoop();
    	}
    	GameInfo gameInfo = new GameInfo(carPositions, 
    			speedList, collisionStatus, carTypes, loopList,hpList);
    	return gameInfo;
    }   

	public GameStatus getGameStatus(int carID) {
		long currentTime = System.nanoTime() / 1000000000;
		this.lastConnection[carID] = currentTime;
		return this.gameStatus;
	}
	
	public void calHp(ServerCar currentCar, float speed){
		if(currentCar.getCollisionStatus()){
			if(currentCar.currentHp-speed <=0){
				currentCar.currentHp = 0;
			}else{
				currentCar.currentHp -= 0.5;
			}
		}
	}

    public int getWinner() {
    	return this.winerID;
    }
    
    public void removeMyself(int carID) {
    	if (gameStatus == GameStatus.READY) {
    		System.out.println("quit from waiting");
    		availableID[carID] = true;
    		if (allQuit()) {
    			resetGame();
    		}
    	}
    	else if (gameStatus == GameStatus.RUNING) {
    		System.out.println("quit from game");
    		carList.get(carID).setCollisionStatus(true);
    		carList.get(carID).setLoopOffLine(-1);//set flag for off-line client
    		quitedCar[carID] = true;
    		if (allQuit()) {
    			resetGame();
    		}
    	}
    	else {
    		System.out.println("quit");
    		quitedCar[carID] = true;
    		if (allQuit()) {
    			//carList = null;
    			resetGame();
    		}
    	}
    }

    // function to judge whether a game should be stopped
    private void judgeGame() {
    	if (gameStatus == GameStatus.RUNING) {
    		int remainCars = 0;
    		int remainCarID = -1;
    		for (int i = 0; i < carList.size(); i++) {
    			ServerCar aCar = carList.get(i);
    			if (aCar.getLoop() == 2) {
    				this.winerID = i;
    				gameStatus = GameStatus.CLOSE;
    				break;
    			}
    			
    			float hp = aCar.currentHp;
        		BigDecimal b = new BigDecimal(hp); 
        		float hpI = b.setScale(0, BigDecimal.ROUND_HALF_UP).floatValue(); 
    			System.out.println(hpI);
    			if (hp > 0f) {
    				remainCarID = i;
    				remainCars++;
    			}
    		}
    		System.out.println(remainCarID+" "+remainCars);
    		if (this.winerID == -1) {
    			if (remainCars <= 1 && this.PLAYER_NUMBER != 1) {
        			this.winerID = remainCarID;
        			gameStatus = GameStatus.CLOSE;
        		}
    			if(this.PLAYER_NUMBER == 1&&carList.get(0).currentHp==0.5f){
    				gameStatus = GameStatus.CLOSE;
    			}
    		}
    	}
    }
    
    // the joined player to get an available ID to register into the game
    private int getID() {
    	for (int i = 0; i < PLAYER_NUMBER; i++) {
    		if (availableID[i]) {
    			availableID[i] = false;
    			return i;
    		}
    	}
    	return -1;
    }
    
    // to judge is there any place for join the game
    private boolean hasPlace() {
    	for (int i = 0; i < PLAYER_NUMBER; i++) {
    		if (availableID[i]) {
    			return true;
    		}
    	}
    	return false;
    }
    
    // to judge is all the players have quit the game
    private boolean allQuit() {
    	for (int i = 0; i < PLAYER_NUMBER; i++) {
    		if (!quitedCar[i]) {
    			return false;
    		}
    	}
    	return true;
    }   
    
    // clear data and reset the game
    private void resetGame() {
		ArrayList<ServerCar> newCarList = new ArrayList<ServerCar>();
		carList = newCarList;
		winerID = -1;
		for (int i = 0; i < PLAYER_NUMBER; i++) {
			availableID[i] = true;
			quitedCar[i] = false;
		}
		gameStatus = GameStatus.READY;
    }

    // the thread to detect the connection between client and server
    class NetworkDetectThread implements Runnable {
    	public NetworkDetectThread(){
    		super();
    	}
    	@Override
    	public void run() {
    		while(true){
    			for (int i = 0; i < carList.size(); i++) {
    				long currentTime = System.nanoTime() / 1000000000;
    				//System.out.println("lastConnection " + i + " "+lastConnection[i]);
    				if (currentTime - lastConnection[i] > 2) {	
    					//System.out.println("User " + i + " has lost connection.");
    					removeMyself(i);
    				}
    			}
    			try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
    		}
    	}
    }

}